/**
 ******************************************************************************
 * @file	cscreen.c
 * @brief	屏幕截图
 *
 * 读取framebuffer内容保存成bmp格式
 *
 * ./cscreen.elf -D /dev/fb0 -f a.bmp -r 10,20,100,200
 * 从设备/dev/fb0坐标10,20开始截取宽100、高200的矩形保存成a.bmp
 *
 * - 2016-11-18,MenglongWoo,MenglongWoo@aliyun.com
 *	- brief
*/
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <getopt.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>

#include "proginfo.h"

struct rect {
	int x;
	int y;
	int w;
	int h;
};
struct rect g_rc = {0};			// 制定截图区域
bool  b_fs = true;			// 是否全屏
char *g_filename = "unname.bmp";
char *g_device = "/dev/fb0";


struct fbinfo {
	int w;
	int h;
	int deep;
	int sz;
	// 色彩编码规则
};

struct csinfo {
	int x;
	int y;
	int w;
	int h;
	int deep;
	int sz;
};

void disp_fbinfo(struct fbinfo *fbi)
{
	printf("%dx%d, %dbpp, screen size = %d\n", fbi->w, fbi->h, fbi->deep, fbi->sz);
}
int getfbinfo(int fd_fb, struct fbinfo *fbi)
{
	struct fb_var_screeninfo vinfo;
	struct fb_fix_screeninfo finfo;
	long int screen_size = 0;
	// Get fixed screen info
	if (ioctl(fd_fb, FBIOGET_FSCREENINFO, &finfo)) {
		printf("Error reading fixed information.\n");
		exit(2);
	}

	// Get variable screen info
	if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &vinfo)) {
		printf("Error reading variable information.\n");
		exit(3);
	}

	// the size of the screen in bytes
	screen_size = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel >> 3;

	fbi->w = vinfo.xres;
	fbi->h = vinfo.yres;
	fbi->deep = vinfo.bits_per_pixel;
	fbi->sz = fbi->w * fbi->h * fbi->deep >> 3;
	return 0;
}


void *mapfb(int fd_fb, struct fbinfo *fbi)
{
	assert(fbi != NULL);
	assert(fbi->sz != 0);
	char *memmap = NULL;

	memmap = (char *)mmap(0, fbi->sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
	if ((int)memmap == -1) {
		printf("Error: failed to map framebuffer device to memory.\n");
		return NULL;
	}
	return memmap;
}

int munmapfb(void *mem, int size)
{
	munmap(mem, size);
	return 0;
}

// 制定区域截图
char readrect(unsigned char *map, unsigned  char *buf, struct fbinfo *fbi, struct csinfo *csi)
{
#if 0
	int deep = csi->deep >> 3;
	for (int i = 0; i < csi->h; i++) {
		memcpy(
		    buf + csi->w * deep * i ,
		    fbi->w * csi->y * deep + map + fbi->w * i * deep  + csi->x * deep,
		    csi->w * deep);
	}
#else
	int deep   = csi->deep >> 3;		// 每个点占用字节
	int bwbyte = csi->w * deep ;		// 图片宽度占用字节
	int fwbyte = fbi->w  * deep;		// 屏幕每行占用字节
	int offh   = fbi->w * csi->y * deep;	// 图片高度偏移字节
	int offw   = csi->x * deep;		// 图片宽度占用字节

	map = map + offw + offh;		// 先移动必要的宽度和高度
	for (int i = 0; i < csi->h; i++) {
		memcpy(buf, map, bwbyte);		// 写入一行图片宽度
		buf += bwbyte;			// 移动一行图片宽度
		map += fwbyte;			// 移动一行屏幕宽度
	}
#endif
	return 0;
}

// 全屏截图
char readoneframe(unsigned char *map, unsigned char *buf, struct fbinfo *fbi)
{
	memcpy(buf, map, fbi->sz);
	return 0;
}

// 截图区域不能超过屏幕区域
static bool _cs_rect_isvaild(struct fbinfo *fbi, struct csinfo *csi)
{
	if(fbi->w < csi->w + csi->x ||
	   fbi->h < csi->h + csi->y) {
		return false;
	}
	return true;
}

/**
 * @brief	获取抓取屏幕区域
 * @param[in]	fbi 屏幕区域
 * @param[out]	csi 返回截图区域
 * @param[in]	rc 期望的截取屏幕区域
 * @param[in]	fullscreen 是否全屏截图
 * @retval	true csi 截图区域有效
 * @retval	false rc指定的区域超过fbi屏幕区域，csi 无效
 * @remarks	由于bmp文件要求：
 *	图片宽度必须是色彩深度的整数倍，为了能让save_bmp实现简单，不去做末尾空白
 *	点插入0，所以在capture_rect对矩形区域过滤，若宽度不满足则扩大截图宽度
 * @see save_bmp
 *
 */
bool capture_rect(struct fbinfo *fbi, struct csinfo *csi, struct rect *rc, bool fullscreen)
{
	if (fullscreen) {	// 抓取矩形
		// 只是防止意外，现实生活中的LCD宽度都是偶数
		fbi->w = (fbi->w & (~(fbi->deep - 1) >> 3)) ;
		// printf("fw %d\n", fbi->w);
		csi->x    = 0,		csi->y    = 0;
		csi->h    = fbi->h,	csi->w    = fbi->w;
	} else {		// 抓取一帧屏幕
		csi->x    = rc->x, 	csi->y    = rc->y;
		csi->h    = rc->h, 	csi->w    = rc->w;
		// 凑够deep的整数倍
		csi->w = csi->w  + (csi->w & ((fbi->deep - 1) >> 3));
		if ( !_cs_rect_isvaild(fbi, csi)) {
			return false;
		}
	}
	csi->deep = fbi->deep;
	csi->sz   = csi->w * csi->h * csi->deep >> 3;
	return true;
}
// 调试保存数据用
char _dbg_save(char *file, char *data, int len)
{
	FILE *fp;
	int ret, offset;
	fp = fopen(file, "wb");
	if (fp == NULL) {
		return -1;
	}

	offset = 0;
	while(offset < len) {
		ret = fwrite(data + offset, 1, len + offset, fp);
		offset += ret;
	}

	fclose(fp);
	return 0;
}




// **************************************************************************
struct scr_dc {
	struct fbinfo *fbi;
	struct csinfo *csi;
	unsigned char *data;
};

typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef long LONG;


typedef struct tagBITMAPFILEHEADER {
	WORD  bfType;
	DWORD bfSize;
	WORD  bfReserved1;
	WORD  bfReserved2;
	DWORD bfOffBits;
} __attribute__((packed)) BITMAPFILEHEADER, *PBITMAPFILEHEADER;

typedef struct tagBITMAPINFOHEADER {
	DWORD biSize;
	LONG  biWidth;
	LONG  biHeight;
	WORD  biPlanes;
	WORD  biBitCount;
	DWORD biCompression;
	DWORD biSizeImage;
	LONG  biXPelsPerMeter;
	LONG  biYPelsPerMeter;
	DWORD biClrUsed;
	DWORD biClrImportant;
} __attribute__((packed)) BITMAPINFOHEADER, *PBITMAPINFOHEADER;

typedef struct tagRGBQUAD {
	BYTE rgbBlue;
	BYTE rgbGreen;
	BYTE rgbRed;
	BYTE rgbReserved;
} __attribute__((packed)) RGBQUAD;


// **************************************************************************
#if 1
// 单个像素转换
#define RGB_565_2_555(_16bit) (((_16bit) & 0xFFC0) >> 1) | ((_16bit) & 0x001F)
void swap565to555(unsigned char *data, int size)
{
	register unsigned int len = size >> 1;
	register unsigned short *pdata = (unsigned short *)data;
	len = 0 - len;
	while(len++) {
		*pdata = RGB_565_2_555(*pdata);
		pdata++;
	}
}
#endif
#if 0
// 以两两像素点转换
#define _2PIXEL_RGB_565_2_555(_32bit) (((_32bit) & 0xFFC0FFC0) >> 1) | ((_32bit) & 0x001F001F);
void  swap565to555(unsigned char *data, int size)
{
	register unsigned int len = size >> 1;
	register unsigned long *plong = (unsigned char *)data;

	len = (size & (~0x3))  >> 2;
	while(len--) {
		*plong = _2PIXEL_RGB_565_2_555(*plong);
		plong++;
	}
}
#endif
int save_bmp(char *filename, struct scr_dc *sdc)
{
	register unsigned int w, h, deep;
	register unsigned char *data;

	w = sdc->csi->w;
	h = sdc->csi->h;
	deep = sdc->csi->deep;
	data = sdc->data;

	BITMAPFILEHEADER file_head;
	BITMAPINFOHEADER info_head;

	FILE *img_fd;
	int data_size;
	data_size = sdc->csi->sz;
	/*initialize bmp structs*/
	file_head.bfType      = 0x4d42;
	file_head.bfSize      = sizeof(file_head) + sizeof(info_head) + sdc->csi->sz;
	file_head.bfReserved1 = file_head.bfReserved2 = 0;
	file_head.bfOffBits   = sizeof(file_head) + sizeof(info_head);

	info_head.biSize          = sizeof(info_head);
	info_head.biWidth         = sdc->csi->w;
	info_head.biHeight        = sdc->csi->h;
	info_head.biPlanes        = 1;
	info_head.biBitCount      = sdc->csi->deep;
	info_head.biCompression   = 0;
	info_head.biSizeImage     = 0;
	info_head.biXPelsPerMeter = 0;
	info_head.biYPelsPerMeter = 0;
	info_head.biClrUsed       = 0;
	info_head.biClrImportant  = 0;
	img_fd = fopen(filename, "wb");
	if (img_fd == NULL) {
		return -1;
	}

	fwrite(&file_head, 1, sizeof(file_head), img_fd);
	fwrite(&info_head, 1, sizeof(info_head), img_fd);
	/*revese img and write to file*/
	swap565to555(data, sdc->csi->sz);
	register unsigned int bwbyte = w * (deep >> 3) ;		// 图片宽度占用字节
	data = data +  w * (h - 0 - 1) * (deep  >> 3);
	while(h--) {
		fwrite(data, 1, bwbyte, img_fd);
		data -= bwbyte;
	}
	fclose(img_fd);
	return 0;
}




// **************************************************************************

static void print_usage(const char *prog)
{
	printf("Usage: %s [-f:r]\n", prog);
	puts("  -D <device>  framebuffer device node\n"
	     "  -f <file>    output bmp file\n"
	     "  -r <rectangle> capture screen rectangle (x,y,w,h)\n"
	    );
	exit(1);
}


static void parse_opts(int argc, char *argv[])
{
	int ret;
	while (1) {
		static const struct option lopts[] = {
			{ "device",  1, 0, 'D' },
			{ "file",  1, 0, 'f' },
			{ "rectangle",   1, 0, 'r' },
			{ NULL, 0, 0, 0 },
		};
		int c;

		c = getopt_long(argc, argv, "D:f:r:", lopts, NULL);

		if (c == -1) {
			break;
		}
		switch (c) {
		case 'D':// framebuffer 设备
			g_device = optarg;
			break;
		case 'f':// 保存文件名
			g_filename = optarg;
			break;
		case 'r':// 保存图片大小
			ret = sscanf(optarg, "%d,%d,%d,%d", &g_rc.x, &g_rc.y, &g_rc.w, &g_rc.h);
			if (ret < 4) {
				printf("rectangle format error (x,y,w,h)\n");
				exit(0);
			}
			b_fs = false;
			break;
		default:
			print_usage(argv[0]);
			break;
		}
	}
}
int main(int argc, char **argv)
{
	int    fd_fb = 0;		// 设备文件
	unsigned char  *fdmap;			// 设备文件已内存映射方式访问
	struct fbinfo fbi;		// framebuffer 设备部分描述
	unsigned char   *data;			// 抓取到的数据

	parse_opts(argc, argv);


	// 打开设备
	fd_fb = open(g_device, O_RDWR);
	if (-1 == fd_fb) {
		printf("Error: cannot open framebuffer device.\n");
		exit(1);
	}

	// 获取屏幕dc
	struct scr_dc sdc;
	// 获取屏幕大小、色彩深度
	getfbinfo(fd_fb, &fbi);
	disp_fbinfo(&fbi);

	// 过滤参数合法性
	struct csinfo csi;
	if ( !capture_rect( &fbi, &csi, &g_rc, b_fs) ) {
		printf("capture rectangle over\n");
		goto _err1;
	}

	// 申请工作区域内存
	data = (unsigned char *)malloc(csi.sz);
	// framebuffer设备内存映射
	fdmap = mapfb(fd_fb, &fbi);
	// 截图
	if (b_fs) {
		readoneframe(fdmap, data, &fbi);
	} else {
		
		readrect(fdmap, data, &fbi, &csi);
	}

	// 释放framebuffer内存映射
	munmapfb(fdmap, fbi.sz);


	// 关闭设备
	close(fd_fb);

	// 屏幕保持成bin文件
	// _dbg_save("fb0.bin", data, csi.sz);

	// 转换成BMP
	sdc.fbi = &fbi;
	sdc.csi = &csi;
	sdc.data = data;
	save_bmp(g_filename, &sdc);
	return 0;
_err1:
	close(fd_fb);

	return -1;
}

MODULE_PROJECT(PRJ_NAME);
#if  defined(PRJ_VERSION) && defined(PRJ_PATCHLEVEL) && defined(PRJ_SUBLEVEL)
	MODULE_VERSION(PRJ_VERSION "." PRJ_PATCHLEVEL "." PRJ_SUBLEVEL);
#endif
MODULE_BUILD(__DATE__ " " __TIME__);
PROG_GITSHA1(__GIT_SHA1__);